#region Namespaces

using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;

#endregion

namespace Vibstudio.X4NET.Xml.Linq
{
    public abstract class XContainer : XNode
    {
        internal XContainer()
        {
        }

        public XNode FirstNode { get; internal set; }

        public XNode LastNode { get; internal set; }

        private void CheckChildType(object o, bool addFirst)
        {
            if (o == null || o is string || o is XNode)
            {
                return;
            }

            IEnumerable ocs = o as IEnumerable;
            if (ocs != null)
            {
                foreach (object oc in ocs)
                {
                    CheckChildType(oc, addFirst);
                }
            }
            else
            {
                throw new ArgumentException("Invalid child type: " + o.GetType());
            }
        }

        public void Add(object content)
        {
            if (content == null)
            {
                return;
            }

            foreach (object o in XUtil.ExpandArray(content))
            {
                if (o == null)
                {
                    continue;
                }

                if (!OnAddingObject(o, false, LastNode, false))
                {
                    var node = XUtil.ToNode(o);
                    OnAddingObject(node);
                    AddNode(node);
                    OnAddedObject(node);
                }
            }
        }

        private void AddNode(XNode n)
        {
            CheckChildType(n, false);
            n = (XNode)XUtil.GetDetachedObject(n);
            n.SetOwner(this);
            if (FirstNode == null)
            {
                LastNode = FirstNode = n;
            }
            else
            {
                LastNode.NextNode = n;
                n.PreviousNode = LastNode;
                LastNode = n;
            }
        }

        public void Add(params object[] content)
        {
            if (content == null)
            {
                return;
            }
            foreach (object o in XUtil.ExpandArray(content))
            {
                Add(o);
            }
        }

        public void AddFirst(object content)
        {
            if (FirstNode == null)
            {
                Add(content);
            }
            else
            {
                FirstNode.AddBeforeSelf(XUtil.ExpandArray(content));
            }
        }

        public void AddFirst(params object[] content)
        {
            if (content == null)
            {
                return;
            }
            if (FirstNode == null)
            {
                Add(content);
            }
            else
            {
                foreach (object o in XUtil.ExpandArray(content))
                {
                    if (!OnAddingObject(o, false, FirstNode.PreviousNode, true))
                    {
                        FirstNode.AddBeforeSelf(o);
                    }
                }
            }
        }

        internal virtual bool OnAddingObject(object o, bool rejectAttribute, XNode refNode, bool addFirst)
        {
            return false;
        }

        public XmlWriter CreateWriter()
        {
            return new XNodeWriter(this);
        }

        public IEnumerable<XNode> Nodes()
        {
            XNode next;
            for (XNode n = FirstNode; n != null; n = next)
            {
                next = n.NextNode;
                yield return n;
            }
        }

        public IEnumerable<XNode> DescendantNodes()
        {
            foreach (XNode n in Nodes())
            {
                yield return n;
                XContainer c = n as XContainer;
                if (c != null)
                {
                    foreach (XNode d in c.DescendantNodes())
                    {
                        yield return d;
                    }
                }
            }
        }

        public IEnumerable<XElement> Descendants()
        {
            foreach (XNode n in DescendantNodes())
            {
                XElement el = n as XElement;
                if (el != null)
                {
                    yield return el;
                }
            }
        }

        public IEnumerable<XElement> Descendants(XName name)
        {
            foreach (XElement el in Descendants())
            {
                if (el.Name == name)
                {
                    yield return el;
                }
            }
        }

        public IEnumerable<XElement> Elements()
        {
            foreach (XNode n in Nodes())
            {
                XElement el = n as XElement;
                if (el != null)
                {
                    yield return el;
                }
            }
        }

        public IEnumerable<XElement> Elements(XName name)
        {
            foreach (XNode n in Nodes())
            {
                XElement el = n as XElement;
                if (el != null && el.Name == name)
                {
                    yield return el;
                }
            }
        }

        public XElement Element(XName name)
        {
            foreach (XNode n in Nodes())
            {
                XElement el = n as XElement;
                if (el != null && el.Name == name)
                {
                    return el;
                }
            }
            return null;
        }

        internal void ReadContentFrom(XmlReader reader, LoadOptions options)
        {
            while (!reader.EOF)
            {
                if (reader.NodeType == XmlNodeType.EndElement)
                {
                    break;
                }
                Add(ReadFrom(reader, options));
            }
        }

        public void RemoveNodes()
        {
            foreach (XNode n in Nodes())
            {
                n.Remove();
            }
        }

        public void ReplaceNodes(object content)
        {
            if (FirstNode == null)
            {
                Add(content);
                return;
            }

            var l = new List<object>();
            foreach (var obj in XUtil.ExpandArray(content))
            {
                l.Add(obj);
            }

            RemoveNodes();
            Add(l);
        }

        public void ReplaceNodes(params object[] content)
        {
            ReplaceNodes((object)content);
        }
    }
}